package org.abratuhi.kvngroupware.pubsub;

import java.util.ArrayList;
import java.util.List;

public class Hub<T> implements SubscriptionStorage<T>{
	
	public final static int HN_BALANCED = 0;
	public final static int HN_UNBALANCED_SPLIT = 1;
	public final static int HN_UNBALANCED_SHARE_LEFT = 2;
	public final static int HN_UNBALANCED_SHARE_RIGHT = 3;
	public final static int HN_UNBALANCED_MERGE_LEFT = 4;
	public final static int HN_UNBALANCED_MERGE_RIGHT = 5;
	
	String attributeId;
	Integer maxSubscriptionsProNode;
	Integer iniNodes;
	Integer maxNodes;
	
	ArrayList<Hubnode<T>> hubnodes = new ArrayList<Hubnode<T>>();

	public Hub(String attrId, int max1, int max2, int ini, Value<T> reference){
		this.attributeId = attrId;
		this.maxSubscriptionsProNode = max1;
		this.maxNodes = max2;
		this.iniNodes = ini;
		
		Value<T> min = reference.getMinValue();
		Value<T> max = reference.getMaxValue();
		
		if(iniNodes == 1){
			addHubnode(new Hubnode<T>(attributeId,min, max));
		}
		else{
			Value<T> step = max.substract(min).divide(iniNodes);
			for(int i=0; i<iniNodes; i++)
				addHubnode(new Hubnode<T>(attributeId, min.add(step.multiply(i)), min.add(step.multiply(i+1))));
		}
		
	}
	
	void addHubnode(Hubnode<T> hubnode){
		hubnodes.add(hubnode);
	}
	
	void removeHubnode(Hubnode<T> hubnode){
		hubnodes.remove(hubnode);
	}
	
	Hubnode<T> findHubnode(Value<T> value){
		for(int i=0; i<hubnodes.size(); i++)
			if(hubnodes.get(i).getRange().contains(value))
				return hubnodes.get(i);
		return null;
	}
	
	void splitHubnode(int index){
		Hubnode<T> hn = hubnodes.get(index);
		//System.out.println("Splitting hubnode: "+hn.toString());
		Value<T> mean = hn.getLowerBound().getMeanTo(hn.getUpperBound());
		
		Hubnode<T> ahn = new Hubnode<T>(hn.getAttributeId(), mean, hn.getUpperBound());
		hn.setUpperBound(mean);
		
		reassign(hn, ahn);
		
		hubnodes.add(index, ahn);
		//System.out.println("Splitting complete: "+hn.toString() + " AND "+ahn.toString());
	}
	
	void mergeHubnodeWithPrevious(int index){
		Hubnode<T> hncurr = hubnodes.get(index);
		Hubnode<T> hnprev = hubnodes.remove(index-1);
		hncurr.setLowerBound(hnprev.getLowerBound());
		for(int i=0; i<hnprev.subscriptions.size(); i++)
			hncurr.addSubscription(hnprev.subscriptions.get(i));
	}
	
	void mergeHubnodeWithNext(int index){
		Hubnode<T> hncurr = hubnodes.get(index);
		Hubnode<T> hnnext = hubnodes.remove(index+1);
		hncurr.setUpperBound(hnnext.getUpperBound());
		for(int i=0; i<hnnext.subscriptions.size(); i++)
			hncurr.addSubscription(hnnext.subscriptions.get(i));
	}
			
	void shareWithPrevious(int index){
		Hubnode<T> hncurr = hubnodes.get(index);
		Hubnode<T> hnprev = hubnodes.remove(index-1);
		Value<T> mean = hnprev.getLowerBound().getMeanTo(hncurr.getUpperBound());
		
		hnprev.setUpperBound(mean);
		hncurr.setLowerBound(mean);
		
		reassign(hnprev, hncurr);
	}
	
	void shareWithNext(int index){
		Hubnode<T> hncurr = hubnodes.get(index);
		Hubnode<T> hnnext = hubnodes.get(index+1);
		Value<T> mean = hncurr.getLowerBound().getMeanTo(hnnext.getUpperBound());
		
		hncurr.setUpperBound(mean);
		hnnext.setLowerBound(mean);
		
		reassign(hncurr, hnnext);
	}
	
	void reassign(Hubnode<T> hnfirst, Hubnode<T> hnsecond){
		for(int i=hnsecond.subscriptions.size()-1; i>=0; i--){
			if(hnsecond.subscriptions.get(i).intersects(attributeId, hnfirst.getRange()))
				hnfirst.addSubscription(hnsecond.subscriptions.get(i));
			if(!hnsecond.subscriptions.get(i).intersects(attributeId, hnsecond.getRange()))
				hnsecond.removeSubscription(hnsecond.subscriptions.get(i));
		}
		
		for(int i=hnfirst.subscriptions.size()-1; i>=0; i--){
			if(hnfirst.subscriptions.get(i).intersects(attributeId, hnsecond.getRange()))
				hnsecond.addSubscription(hnfirst.subscriptions.get(i));
			if(!hnfirst.subscriptions.get(i).intersects(attributeId, hnfirst.getRange()))
				hnfirst.removeSubscription(hnfirst.subscriptions.get(i));
		}
	}
	
	void balanceHubnode(int index, int balancing){
		switch(balancing){
		case HN_UNBALANCED_SPLIT: splitHubnode(index); break;
		case HN_UNBALANCED_SHARE_LEFT: shareWithPrevious(index); break;
		case HN_UNBALANCED_SHARE_RIGHT: shareWithNext(index); break;
		case HN_UNBALANCED_MERGE_LEFT: break;
		case HN_UNBALANCED_MERGE_RIGHT: break;
		default: break;
		}
	}
	
	int getHubnodeBalance(int index){
		Hubnode<T> hn = hubnodes.get(index);
		if(hn.subscriptions.size() > maxSubscriptionsProNode) return HN_UNBALANCED_SPLIT;
		else if(index > 0 && hubnodes.get(index-1).subscriptions.size()*2 < hn.subscriptions.size()) return HN_UNBALANCED_SHARE_LEFT;
		else if(index < hubnodes.size()-1 && hubnodes.get(index+1).subscriptions.size()*2 < hn.subscriptions.size()) return HN_UNBALANCED_SHARE_RIGHT;
		return HN_BALANCED;
	}

	@Override
	public void addSubscription(Subscription<T> sub) {
		int balancing = 0;
		int index = 0;
		//System.out.println("HUB ["+attributeId+"] adds subscription: "+sub.toString());
		for(int i=0; i<hubnodes.size(); i++)
			if(sub.intersects(attributeId, hubnodes.get(i).getRange())){
				hubnodes.get(i).addSubscription(sub);
				balancing = getHubnodeBalance(i);
				index = 0;
				//System.out.println(hubnodes.get(i).toString() + "; balancing status: "+balancing);
			}	
			//else
				//System.out.println(hubnodes.get(i).toString() + " won't host subscription "+sub.toString());
		if(balancing != HN_BALANCED) balanceHubnode(index, balancing);
	}

	@Override
	public List<Subscription<T>> findMatchingSubscriptions(Publication<T> pub) {
		ArrayList<Subscription<T>> result = new ArrayList<Subscription<T>>();
		for(int i=0; i<hubnodes.size(); i++)
			result.addAll(hubnodes.get(i).findMatchingSubscriptions(pub));
		return result;
	}

	@Override
	public List<Subscription<T>> findSubcriptions(String subscriberId) {
		ArrayList<Subscription<T>> result = new ArrayList<Subscription<T>>();
		for(int i=0; i<hubnodes.size(); i++)
			result.addAll(hubnodes.get(i).findSubcriptions(subscriberId));
		return result;
	}

	@Override
	public void removeSubscription(Subscription<T> sub) {
		for(int i=0; i<hubnodes.size(); i++)
			hubnodes.get(i).removeSubscription(sub);
	}

	@Override
	public void removeSubscriptions(String subscriberId) {
		for(int i=0; i<hubnodes.size(); i++)
			hubnodes.get(i).removeSubscriptions(subscriberId);
	}

	@Override
	public void updateSubscription(Subscription<T> sub) {
		for(int i=0; i<hubnodes.size(); i++)
			hubnodes.get(i).updateSubscription(sub);
	}
}
